feat: add raw_json to the webhook event and surface unknown action attempt statuses - #489
Draft
razor-x wants to merge 6 commits into
Draft
feat: add raw_json to the webhook event and surface unknown action attempt statuses#489razor-x wants to merge 6 commits into
razor-x wants to merge 6 commits into
Conversation
…uses Seam adds event types, action types, and error codes between SDK releases, so a payload this version does not recognize should stay readable rather than cost the caller the whole response. A list property the API sends as a scalar no longer fails the whole response. The generated classes mapped these with array_map, which raises a TypeError when handed anything but an array, so a single unexpected field took down every other field alongside it. They now route through Seam\Parse::to_list, which reads a non-list as empty. Waiting on an action attempt whose status is neither pending, success, nor error raises the new ActionAttemptUnknownStatusError. The resolver previously treated an unrecognized status as non-terminal and polled until the deadline, then reported a timeout that misdescribed what happened. The error subclasses ActionAttemptError, so existing handlers for that base keep working. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA
The added comments ran to roughly double the density of the code around them and mostly restated what the line below already said. Kept the ones carrying information the code cannot: why svix/util has to be required, why both key shapes are accepted after symbolize_names, and why array_map needed replacing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA
from_json builds only the properties it was generated for, so a field Seam adds
to an existing event between SDK releases is unreachable. SeamWebhook::verify()
now returns an event carrying raw_json():
json_decode($event->raw_json())->a_field_this_version_predates;
Defined on the base Event, so the variants inherit it and the unrecognized-event
fallback answers the same call. Every path reaches events through
Event::from_json, so the payload is captured in one place.
Scoped to events. It is there for the verify return, not as a general accessor
on every resource. A method rather than a property because the call is where the
serialization happens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA
Handling values the SDK does not recognize is forward compatibility. Tolerating payloads that are malformed rather than merely new is a bug being swallowed, and the two were conflated here. Removes Seam\Parse and restores array_map for list properties. A null list was already read as empty, so the guard only caught a list sent as a scalar, which is a defect worth surfacing rather than silently reading as empty. Unrecognized handling is untouched: unknown event types still use the base Event class, unknown error codes the base error class, and unknown enum values read as themselves. ActionAttemptUnknownStatusError stays, because the action attempt contract is strict and returning an unrecognized status as a success reports something the SDK cannot vouch for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The two rules
1. Runtime does not explode on values the SDK does not recognize. Seam adds event types, action types, error codes, and enum values between releases. Reading them must not raise; writing logic against them is what an upgrade is for.
event_typeEventclasserror_code/warning_code$device->device_type === "future_lock")statusThis already held — PHP models enum-typed properties as plain
stringand only uses the generated enums fortryFromdispatch, and all 59 discriminated unions carry adefault =>arm. The tests pin it.2. The webhook event carries the payload it was parsed from.
from_jsonbuilds only the properties it was generated for, so a field Seam adds to an existing event is otherwise unreachable. Defined on the baseEvent, so the variants inherit it and the unrecognized-event fallback answers the same call. Every path reaches events throughEvent::from_json, so the payload is captured in one place.Scoped to events — it is there for the
verifyreturn, not as a general accessor on every resource, and a test assertsDevicedoes not have it. A method rather than a property because the call is where the serialization happens.Action attempts
ActionAttemptUnknownStatusErroris raised when an attempt reports a status that is neither pending, success, nor error. The resolver previously treated an unrecognized status as non-terminal and polled until the deadline, then reported a timeout that misdescribed what happened. It subclassesActionAttemptError, so existing handlers for that base keep working.Not included
Tolerance for malformed payloads. An earlier revision of this branch routed list properties through a helper so that
errors: "oops"read as empty instead of raising aTypeError. That is a defect being swallowed rather than forward compatibility — a null list was already read as empty — so it was reverted andarray_maprestored.283 tests, 645 assertions.
composer lintclean.🤖 Generated with Claude Code
https://claude.ai/code/session_01M2kJ4nGaM8imZCVMEKjmXA